﻿using System;
using System.Diagnostics;
using Bettzueche.RLGlue.Generics;
using generic = Bettzueche.RLGlue.Generics;

namespace Bettzueche.RLGlue.Codec.Network {
    
    /// <summary>
    /// Middleware Schnittstelle zur Ausführung eines Agenten im RLGlue Kontext.
    /// </summary>
    /// <typeparam name="A">Datentyp der Aktionen; Sollte int, double oder char sein</typeparam>
    /// <typeparam name="O">Datentyp der Zustände; Sollte int, double oder char sein</typeparam>
    /// <remarks>
    /// This Code is based on the Java RL-Glue http://rl-glue-ext.googlecode.com/ ,Copyright (C) 2007, Brian Tanner.
    /// </remarks>
    class GenericClientAgent<A,O> : AbstractClientAgent
        where A : struct
        where O : struct
    {

        /// <summary>Referenz auf die Agenten Implementierung.</summary>
        protected IGenericAgent<A,O> theAgent;
        
        /// <summary>
        /// 
        /// </summary>
        /// <param name="theAgent"></param>
        /// <exception cref="ArgumentNullException"></exception>
        public GenericClientAgent(IGenericAgent<A,O> theAgent) {
            if (theAgent == null)
                throw new ArgumentNullException();
            this.theAgent = theAgent;
            this.network = new Network();
        }

        #region RMI-Handler
        
        /// <summary>
        /// Handler für eine AgentInit Nachricht von RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Auslesen des Parameters (task_spec) und agent Methode aufrufen.
        /// Bestätigung des Methodenaufrufs mit Rückgabewert (void) vorbereiten.
        /// </remarks>
        protected override void onAgentInit()/* throws UnsupportedEncodingException*/ {
            String taskSpec = network.GetString();
            theAgent.Agent_init(taskSpec);

            network.ClearSendBuffer();
            network.PutInt(Protocol.kAgentInit);
            //BTANNER: Sept 16 2008.. why are we putting this here?
            //OH, it's the size!  Ok.  I think.
            network.PutInt(0); // No data following this header
        }

        /// <summary>
        /// Handler für eine AgentStart Nachricht von RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Auslesen des Parameters (Observation) und agent Methode aufrufen.
        /// Bestätigung der AgentStart mit zurückgegebener action vorbereiten.
        /// </remarks>
        protected override void onAgentStart() {
            Debug.WriteLine("\tonAgentStart()");
            Observation<O> observation = network.GetObservation<O>();
            Debug.WriteLine("\t\tgot observation: " + observation);
            generic.Action<A> action = theAgent.Agent_start(observation);
            Debug.WriteLine("\t\tgot action");

            int size = Network.SizeOf(action);

            network.ClearSendBuffer();
            network.PutInt(Protocol.kAgentStart);
            network.PutInt(size);
            network.PutAction(action);
        }

        /// <summary>
        /// Handler für eine AgentStep Nachricht von RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Auslesen des Parameters (Reward) und agent Methode aufrufen.
        /// Bestätigung der AgentStep mit Rückgabewert (Action) vorbereiten.
        /// </remarks>
        protected override void onAgentStep() {
            double reward = network.GetDouble();
            Observation<O> observation = network.GetObservation<O>();
            generic.Action<A> action = theAgent.Agent_step(reward, observation);

            int size = Network.SizeOf(action);
            network.ClearSendBuffer();
            network.PutInt(Protocol.kAgentStep);
            network.PutInt(size);
            network.PutAction(action);
        }

        /// <summary>
        /// Handler für eine AgentEnd Nachricht von RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Auslesen des Parameters (Reward) und agent Methode aufrufen.
        /// Bestätigung der AgentStep mit Rückgabewert (void) vorbereiten.
        /// </remarks>
        protected override void onAgentEnd() {
            //int size = network.getInt();
            double reward = network.GetDouble();

            theAgent.Agent_end(reward);

            network.ClearSendBuffer();
            network.PutInt(Protocol.kAgentEnd);
            network.PutInt(0); // No data in this packet
        }

        /// <summary>
        /// Handler für eine AgentCleanup Nachricht von RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Auslesen des Parameters (void) entfällt. Agent Methode aufrufen.
        /// Bestätigung der AgentCleanup mit Rückgabewert (void) vorbereiten.
        /// </remarks>
        protected override void onAgentCleanup() {
            theAgent.Agent_cleanup();

            network.ClearSendBuffer();
            network.PutInt(Protocol.kAgentCleanup);
            network.PutInt(0); // No data in this packet
        }

        /// <summary>
        /// Handler für eine AgentMessage Nachricht von RLGlue.exe.
        /// </summary>
        /// <remarks>
        /// Auslesen des Parameters (message string) und agent Methode aufrufen.
        /// Bestätigung des Aufrufs mit Rückgabewert (reply string) vorbereiten.
        /// </remarks>
        protected override void onAgentMessage()/* throws UnsupportedEncodingException*/ {
            String message = network.GetString();
            String reply = theAgent.Agent_message(message);

            network.ClearSendBuffer();
            network.PutInt(Protocol.kAgentMessage);
            network.PutInt(Network.SizeOf(reply));
            network.PutString(reply);
        }

        #endregion

    }
}
